import logging
from collections import defaultdict
from os import makedirs
from pathlib import Path
from typing import Union, Dict

from tqdm import tqdm

from .fileparser import DependencyParser
from .projectloader import CProjectLoader
from .projectparser import ProjectParser


class ProjectAnalyzer:
    def __init__(self, project_path: Union[str, Path]):
        self.logger = logging.getLogger("analyzer")

        self.project_path = Path(project_path).resolve()

        # 加载项目到内存
        self.project = CProjectLoader(self.project_path)
        # 计算文件依赖图
        self.file_dependency = ProjectParser(self.project, progress=True)
        # 计算函数依赖图
        self.function_dependencies: Dict[str, DependencyParser] = {}

        total_work = len(self.file_dependency.files)

        progress_bar = tqdm(total=total_work, desc="Building Function Dependency Graph")

        for module, data in self.file_dependency.graph.nodes(data=True):
            if data['type'] == 'module':
                self.function_dependencies[module] = DependencyParser(self.project, data['h_file'], data['c_file'])
                progress_bar.update(1)
            else:
                for name, h_file, c_file in zip(data['name'], data['h_file'], data['c_file']):
                    self.function_dependencies[name] = DependencyParser(self.project, h_file, c_file)
                    progress_bar.update(1)

    def visualize(self):
        project_name = self.project_path.stem

        graph_path = Path('./') / f'{project_name}_graph'
        makedirs(graph_path, exist_ok=True)

        self.file_dependency.visualize(str(graph_path / f'{project_name}_graph'))
        self.file_dependency.visualize(str(graph_path / f'{project_name}_origin_graph'), True)

        for module, dependency in self.function_dependencies.items():
            dependency.visualize(str(graph_path / f'{module}_graph'))
            dependency.visualize(str(graph_path / f'{module}_origin_graph'), True)

    def print_parallel_levels(self):
        print('项目：', end='')
        self.file_dependency.print_parallel_level()
        print()
        for module, dependency in self.function_dependencies.items():
            print(f'{module}模块：', end='')
            dependency.print_parallel_level()
            print()

    @staticmethod
    def _get_node(graph, node):
        return graph.nodes[node]

    def _translate_levels(self, graph, levels, keys: list):
        group_keys = keys.copy()
        group_keys.remove('type')
        group_keys.append('name')

        output_levels = []
        for level in levels:
            output_level = []
            for node in level:
                node_info = self._get_node(graph, node)
                if node_info['type'].endswith('group'):
                    group = []
                    for data in zip(*map(lambda x: node_info[x], group_keys)):
                        info = dict(zip(group_keys, data))
                        info['type'] = node_info['type'][:-6]
                        name = info.pop('name')
                        group.append((name, info))

                    output_level.append(group)
                else:
                    info = {k: node_info[k] for k in keys}
                    output_level.append((node, info))
            output_levels.append(output_level)
        return output_levels

    def get_parallel_levels(self):
        # type, c_file, h_file
        file_levels = self.file_dependency.get_parallel_level()
        file_levels = self._translate_levels(self.file_dependency.graph, file_levels, ['type', 'c_file', 'h_file'])

        module_levels = {}
        for module, dependency in self.function_dependencies.items():
            # type, extern, location
            unit_levels = dependency.get_parallel_level()
            unit_levels = self._translate_levels(dependency.graph, unit_levels, ['type', 'extern', 'location'])
            module_levels[module] = unit_levels

        return file_levels, module_levels

    def get_file_unit_info_map(self):
        group_keys = ['name', 'extern', 'location']
        all_map = defaultdict(dict)
        for module, dependency in self.function_dependencies.items():
            # type, extern, location
            for unit, unit_info in dependency.graph.nodes(data=True):
                if unit_info['type'].endswith('group'):
                    for data in zip(*map(lambda x: unit_info[x], group_keys)):
                        info = dict(zip(group_keys, data))
                        info['type'] = unit_info['type'][:-6]
                        name = info.pop('name')
                        all_map[module][name] = info
                else:
                    all_map[module][unit] = unit_info
        return all_map
